const TAG: string = 'BT_Demo[Logic]'


enum CommState {
    NO_COMM,
    GET_MY_CON,
    GET_MY_CON_SEND_COMM_WAIT_CONFIRM,
    GET_MY_CON_WAIT_DISCON_CON,
    TAKE_MY_CON,
    TAKE_MY_CON_WAIT_DISCON,
    TAKE_MY_CON_SEND_COMM_WAIT_CONFIRM,
    GET_REMOTE_CON,
    TAKE_REMOTE_CON,
}

enum Const {
    IMMEDIATELY = 1,
    PERIOD_TX_STATUS = 10,
    TIMEOUT_CON = 10,
    TIMEOUT_CONFIRM = 10,
    SEND_ATTEMPT = 10,
    TOAST_TIME = 50
}

enum Command {
    NO_COMMAND,
    NO_CON = 'No connect',
    CON_MAC = 'Connected ',
    TAKE = 'Take connect ',
    GET = 'Get connect ',
    TAKE_CONFIRM = 'Confirm to take',
    GET_CONFIRM = 'Confirm to get',
    GET_CONFIRM_DISCONNECT = 'Confirm disconnect',
}

class Logic {
    distribute:any = null
    bluetooth:any = null
    subscribe: any = null
    bundleName: string = ''
    abilityName: string = ''
    appName: string = ''

    commState: CommState = CommState.NO_COMM
    started = false
    distributeConnectTryCounter = 0
    myComMAC: string = null // null - no connections
    remoteComMAC: string = null // null - no connections
    noChangeTxCounter = 0       // If not changing, then transmit only one times per second
    waitCounter = 0     // use for count waiting periods
    attemptCounter = 0
    remoteExchangeTimeout = 0;
    inputCommand: Command = Command.NO_COMMAND
    savedMac: string = null     // use for second param on inputCommand or for saving MAC address during GET
    toastState = ''
    toastStateTimer = 0
    stateListener: (getEn: boolean, takeEn: boolean, message: string)=>void = null
    bubbleShow: (text: string) =>void
    takeEn: boolean = false
    init(distribute: any, bluetooth: any, subscribe: any, bundleName: string, abilityName: string, appName: string){
        this.distribute = distribute
        this.bluetooth = bluetooth
        this.subscribe = subscribe
        this.bundleName = bundleName
        this.abilityName = abilityName
        this.appName = appName
        this.distribute.init(
            this.onMessageReceivedListener.bind(this),
            this.onConnectionFinished.bind(this),
            this.setMessage.bind(this),
            bundleName, abilityName, appName
        )
    }

    public start() {
        console.info(`${TAG} start`)
        this.subscribe(true, this.listenersCallBack.bind(this))
        setTimeout(() => {
            console.info(`${TAG} try to connect`)
            this.distributeConnectTryCounter = 0
            this.distribute.connect()
        }, 500)
        if (!this.started) {
            this.proc(true); this.started = true}
    }

    public stop(){
        console.info(`${TAG} stop`)
        this.subscribe(false)
        this.distribute.stop()
    }

    private listenersCallBack(state: boolean, parity = 255) {
        console.info(`${TAG} listenersCallBack state=${state} parity=${parity} takeEn=${this.takeEn}`)
        if (state === false && this.takeEn == true) {
            this.takeConnection()
        }
    }

    private onConnectionFinished(isDistributed: boolean) {
        if (!this.distribute.isDistributed) {
            if (this.distributeConnectTryCounter < 3) {
                this.distributeConnectTryCounter++
                setTimeout(() => {
                    this.distribute.connect()
                }, 100)
            } else {
                this.setMessage("Connecting failed")
            }
        }
    }

    setStateListener(stateListener: (getEn: boolean, takeEn: boolean)=>void) {this.stateListener = stateListener}
    setBubbleShow(bubbleShow: (text: string) =>void) {this.bubbleShow = bubbleShow}

    getConnection (){if (this.commState === CommState.NO_COMM) this.setCommState(CommState.GET_MY_CON ) }
    takeConnection(){if (this.commState === CommState.NO_COMM) this.setCommState(CommState.TAKE_MY_CON) }

    setMessage(text:string) {
        if (text !== '') {
            if (this.bubbleShow != undefined) {
                this.bubbleShow(text)
            }
            this.toastState = text
        }
    }

    onMessageReceivedListener(val: string) {
        var mac: string = ''
        this.inputCommand = Command.NO_COMMAND
        if (val.startsWith(Command.NO_CON          )) {this.inputCommand = Command.NO_CON}
        else if (val.startsWith(Command.CON_MAC     )) {this.inputCommand = Command.CON_MAC}
        else if (val.startsWith(Command.TAKE_CONFIRM)) {this.inputCommand = Command.TAKE_CONFIRM}
        else if (val.startsWith(Command.GET_CONFIRM )) {this.inputCommand = Command.GET_CONFIRM}
        else if (val.startsWith(Command.GET         )) {this.inputCommand = Command.GET}
        else if (val.startsWith(Command.TAKE        )) {this.inputCommand = Command.TAKE}
        //for (let cmd in Command) {
        //    this.inputCommand = cmd // this line get error "Type 'string' is not assignable to type 'Command'."
        //}
        mac = val.substring(this.inputCommand.toString().length)
        if (mac.length === 17) {
            this.savedMac = mac
        }
        if (this.inputCommand == Command.NO_CON) {
            if (this.remoteComMAC !== null) {
                console.info(`${TAG} change remote state ${this.remoteComMAC} -> No conect`)
                this.remoteComMAC = null
            }
        } else if (this.inputCommand == Command.CON_MAC) {
            if (this.remoteComMAC == null) {
                console.info(`${TAG} change remote state No conect -> ${mac}`)
                this.remoteComMAC = mac
            }
        } else if (this.inputCommand !== Command.NO_COMMAND) {
            console.info(`${TAG} inputCommand ${this.inputCommand} ${mac} ${this.savedMac}`)
            this.proc(false)
        }
        this.inputCommand = Command.NO_COMMAND
    }

    setCommState(newState: CommState, timeout: Const = 0) {
        console.info(`${TAG} setCommState ${CommState[this.commState]} -> ${CommState[newState]}`)
        this.inputCommand = Command.NO_COMMAND      // current command was processed
        this.commState = newState
        this.attemptCounter = 0
        this.waitCounter = timeout
        this.proc(false)
    }

    connect(deviceId: string, con: boolean) {
        console.info(`${TAG} connect deviceId=${deviceId} con=${con}`)
        this.bluetooth.connect(deviceId, con)
    }

    proc(needUpdate: boolean) {
        if (!this.distribute.isDistributed) {
            // Remote device is not connected
            this.bluetooth.update()
            setTimeout(() => {
                this.proc(needUpdate)
            }, 500)
            return
        }

        if (this.waitCounter !== 0) this.waitCounter--
        if (this.toastStateTimer !== 0) this.toastStateTimer--
        if (needUpdate) {
            this.bluetooth.update()
            let ConState = this.bluetooth.getConnectionState()
            if (this.myComMAC !== ConState) {
                console.info(`${TAG} myComMAC ${this.myComMAC} -> ${ConState}`)
                this.myComMAC = ConState
                this.noChangeTxCounter = 0 // Transmit if changed
            }
        }

        switch (this.commState) {
            case CommState.NO_COMM:
                switch (this.inputCommand) {
                    case Command.TAKE:
                        this.setCommState(CommState.TAKE_REMOTE_CON);
                        break
                    case Command.GET:
                        this.setCommState(CommState.GET_REMOTE_CON);
                        break
                }
                break
            case CommState.TAKE_MY_CON: // We try to take connection
            // 1 save MAC and disconnect my MAC
            // 2 wait disconnect state
            // 3 send command Command.TAKE
            // 4 wait confirm Command.TAKE_CONFIRM
                this.savedMac = this.myComMAC
                this.connect(this.myComMAC, false)
                this.setCommState(CommState.TAKE_MY_CON_WAIT_DISCON, Const.TIMEOUT_CON)
                break
            case CommState.TAKE_MY_CON_WAIT_DISCON:
            // 2 wait disconnect state
                if (this.myComMAC === null) {
                    // Disconnection ready
                    this.setCommState(CommState.TAKE_MY_CON_SEND_COMM_WAIT_CONFIRM, Const.IMMEDIATELY)
                    break
                } else if (this.waitCounter == 0) {
                    console.info(`${TAG} TAKE_MY_CON_WAIT_DISCON ERROR`)
                    this.setMessage('Take disconnection ERROR')
                    this.setCommState(CommState.NO_COMM)
                }
                break
            case CommState.TAKE_MY_CON_SEND_COMM_WAIT_CONFIRM:
            // 3 send command Command.TAKE
            // 4 wait confirm Command.TAKE_CONFIRM
                if (this.inputCommand == Command.TAKE_CONFIRM) {
                    this.setMessage('Take READY')
                    this.setCommState(CommState.NO_COMM)
                } else if (this.waitCounter == 0) {
                    this.waitCounter = Const.TIMEOUT_CONFIRM
                    if (this.attemptCounter < Const.SEND_ATTEMPT) {
                        this.attemptCounter++
                        console.info(`${TAG} TAKE_MY_CON_SEND_COMM_WAIT_CONFIRM SEND_COMM`)
                        this.distribute.dataTransmit('expression', Command.TAKE + this.savedMac)
                        this.noChangeTxCounter = Const.PERIOD_TX_STATUS // To delay the transmission of status
                    }
                    else {
                        console.info(`${TAG} TAKE_MY_CON_SEND_COMM_WAIT_CONFIRM ERROR`)
                        this.setMessage('Take transmission ERROR')
                        this.setCommState(CommState.NO_COMM)
                    }
                }
                break
            case CommState.GET_MY_CON: // We try to get connection
            // 1 save mac
            // 2 send command Command.GET
            // 3 wait confirm Command.GET_CONFIRM
            // 4 wait remote disconnect state
            // 5 connect to saved mac
                this.savedMac = this.remoteComMAC
                this.setCommState(CommState.GET_MY_CON_SEND_COMM_WAIT_CONFIRM, Const.IMMEDIATELY)
                break
            case CommState.GET_MY_CON_SEND_COMM_WAIT_CONFIRM:
            // 2 send command Command.GET
            // 3 wait confirm Command.GET_CONFIRM
                if (this.inputCommand == Command.GET_CONFIRM) {
                    this.setCommState(CommState.GET_MY_CON_WAIT_DISCON_CON, Const.TIMEOUT_CON+Const.TIMEOUT_CONFIRM)
                } else if (this.waitCounter === 0) {
                    this.waitCounter = Const.TIMEOUT_CONFIRM
                    if (this.attemptCounter < Const.SEND_ATTEMPT) {
                        this.attemptCounter++
                        console.info(`${TAG} GET_MY_CON_SEND_COMM_WAIT_CONFIRM SEND_COMM`)
                        this.distribute.dataTransmit('expression', Command.GET)
                        this.noChangeTxCounter = Const.PERIOD_TX_STATUS // To delay the transmission of status
                    }
                    else {
                        console.info(`${TAG} GET_MY_CON_SEND_COMM_WAIT_CONFIRM ERROR`)
                        this.setMessage('Get disconnection ERROR')
                        this.setCommState(CommState.NO_COMM)
                    }
                }
                break
            case CommState.GET_MY_CON_WAIT_DISCON_CON:
            // 4 wait remote disconnect state
            // 5 connect to saved mac
                if (this.remoteComMAC === null) {
                    // Disconnection ready
                    console.info(`${TAG} GET_MY_CON_WAIT_DISCON_CON READY`)
                    this.connect(this.savedMac, true)
                    this.setMessage('Get READY')
                    this.setCommState(CommState.NO_COMM)
                } else if (this.waitCounter === 0) {
                    console.info(`${TAG} GET_MY_CON_WAIT_DISCON_CON ERROR`)
                    this.setMessage('Get connection ERROR')
                    this.setCommState(CommState.NO_COMM)
                }
                break
            case CommState.TAKE_REMOTE_CON: // We answer by the command TAKE_MY_CON
            // 1 connect to input (saved) mac and send confirm
                this.connect(this.savedMac, true)
                this.distribute.dataTransmit('expression', Command.TAKE_CONFIRM)
                this.noChangeTxCounter = Const.PERIOD_TX_STATUS // To delay the transmission of status
                this.setCommState(CommState.NO_COMM)
                break
            case CommState.GET_REMOTE_CON: // We answer by the command GET_MY_CON
            // 1 disconnect input (saved) mac and send confirm
                this.connect(this.savedMac, false)
                this.distribute.dataTransmit('expression', Command.GET_CONFIRM)
                this.noChangeTxCounter = Const.PERIOD_TX_STATUS // To delay the transmission of status
                this.setCommState(CommState.NO_COMM)
                break
        }
        if (this.noChangeTxCounter <= 0) {
            this.noChangeTxCounter = Const.PERIOD_TX_STATUS // Transmit only one times per second
            let myStatus = this.myComMAC === null ? Command.NO_CON : Command.CON_MAC + this.myComMAC
            this.distribute.dataTransmit('expression', myStatus)
            console.info(`${TAG} Tx ${myStatus}`)
        }
        if (needUpdate) {
            this.noChangeTxCounter--
            if (this.remoteExchangeTimeout > 100) {
                this.remoteExchangeTimeout++
            }
            if (this.toastState !== '') {
                if (this.toastStateTimer === 0) {
                    this.toastStateTimer = Const.TOAST_TIME
                }
                this.toastStateTimer--
                if (this.toastStateTimer === 1) {
                    this.toastState = ''
                    this.toastStateTimer = 0
                }
            }
            setTimeout(() => {
                //console.info(`${TAG} setTimeout this.proc(true)`)
                this.proc(true)
            }, 100)
        }
        if (this.stateListener !== null) {
            this.stateListener(
                this.remoteComMAC !== null && this.myComMAC === null, // getEn
                this.takeEn = (this.remoteComMAC === null && this.myComMAC !== null), // takeEn
                ((this.myComMAC !== null) ? `My con ${this.myComMAC}` : `My no con`) +
                '   ' +
                ((this.remoteComMAC !== null) ? `Remote con ${this.remoteComMAC}` : `Remote no Con`) +
                '   ' +
                this.toastState
            )
        }
    }
}

export let logic = new Logic()